package com.crazyteam.genid.service.impl;
import com.crazyteam.genid.constant.GenIdConstant;
import com.crazyteam.genid.exception.LoadPrimaryKeyException;
import com.crazyteam.genid.exception.UndeclaredPrimaryKeyException;
import com.crazyteam.genid.mapper.PrimaryKeyMapper;
import com.crazyteam.genid.model.KeyBuffer;
import com.crazyteam.genid.model.KeyInfo;
import com.crazyteam.genid.model.PrimaryKeyVo;
import com.crazyteam.genid.service.PrimaryKeyService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import javax.annotation.Resource;
import javax.naming.Name;

/**
 * @Desc
 * @Author guoyang
 * @CreateTime 2016-3-3 上午10:30:02
 */
@Service("primaryKeyService")
public class PrimaryKeyServiceImpl implements PrimaryKeyService {

    private static final Logger log = LoggerFactory.getLogger(PrimaryKeyServiceImpl.class);


//    private Executor expandKeyExecutor;
//    private Executor loadPrepBuffexecutor;
//    private PrimaryKeyDao primaryKeyDao;
    @Autowired
    private PrimaryKeyMapper primaryKeyMapper;

    @Async("genpkThreadPool")
    public void asynExpandKey(KeyInfo keyInfo){
        try {
            // 检查主缓冲区
            if (!keyInfo.getCurrBuff().hasNext()) {
//                expandTask.syncExpandKey();
//                new ExpandTask(this, keyInfo).syncExpandKey();
//                expandKeyExecutor.execute(new ExpandThread(this, keyInfo));
                syncExpandKey(keyInfo);
            }
        } catch (Exception e) {
            log.warn("maybe expandKeyExecutor has be full,message:" + e.getMessage());
        }
        // 2、检查扩充预备缓冲区
       asynLoadPrepBuff(keyInfo);
    }
    @Async("genpkThreadPool")
    public void asynLoadPrepBuff(final KeyInfo keyInfo){
        try {
            if (keyInfo.getPrepBuffs().isEmpty()) {

//                loadPrepBuffexecutor.execute(new LoadPrepBuffThread(this, keyInfo));
//                new ExpandTask(this, keyInfo).syncExpandPreKey();
//                expandTask.syncExpandPreKey();
                loadPrepBuff(keyInfo);
            }
        } catch (Exception e) {
            log.warn("maybe loadPrepBuffexecutor has be full, message:" + e.getMessage());
        }
    }

    public void syncExpandKey(final KeyInfo keyInfo){
        // 如果没有可用的主键，则扩充
        if (!keyInfo.getCurrBuff().hasNext()) {
            synchronized (keyInfo.currBuffLock) {
                // 扩展主缓冲区
                keyInfo.getCurrBuff().copy(expandCurrBuff(keyInfo));
            }
        }
    }

    /**
     * 扩展主缓冲区
     * @param keyInfo
     */
    public KeyBuffer expandCurrBuff(final KeyInfo keyInfo){
        // 如果没有可用的主键，则扩充
        if (!keyInfo.getCurrBuff().hasNext()) {
            // 从预备缓冲区poll一个buff
            KeyBuffer buff = keyInfo.getPrepBuffs().poll();
            // 如果预备缓冲区拿出来为空，则从数据库拿取
            if(buff == null){
                buff = loadBuff(keyInfo.getPrimaryKeyName());
            }
            return buff;
        }else{
            return keyInfo.getCurrBuff();
        }
    }

    public void loadPrepBuff(KeyInfo keyInfo){
        if (keyInfo.getPrepBuffs().isEmpty()) {
            KeyBuffer buff = loadBuff(keyInfo.getPrimaryKeyName());
            keyInfo.getPrepBuffs().add(buff);
        }
    }

    /**
     * 加载缓冲区
     */
    public KeyBuffer loadBuff(String primaryKeyName){
        KeyBuffer keyBuff = new KeyBuffer();
        PrimaryKeyVo primary = loadPrimaryKey(primaryKeyName);
        keyBuff.setCurrent(primary.getCurrentValue());
        keyBuff.setMin(primary.getCurrentValue());
        keyBuff.setMax(primary.getCurrentValue() + primary.getStep());
        return keyBuff;
    }

    /**
     * 读取缓冲区， 1、查询当前数据库的值 2、更新当前值，更新为当前值+步长，用当前值做乐观锁 3、如果更新失败(!=1)则重复
     * 1、2步，重复GenIdConstant.RETRY_COUNT次 4、如果1、查询不到则抛异常 5、如果3次均更新失败则抛异常
     * 
     * @param primaryKeyName
     * @return
     */
    private PrimaryKeyVo loadPrimaryKey(String primaryKeyName){
        for (int i = 0; i < GenIdConstant.RETRY_COUNT; i++) {
            PrimaryKeyVo primaryKeyVo = primaryKeyMapper.queryPrimaryKey(primaryKeyName);
            // 如果数据库查不到则
            if (primaryKeyVo == null) {
                String msg = "primayKeyName：" + primaryKeyName + " undeclared，please check the db config";
                throw new UndeclaredPrimaryKeyException(msg);
            }
            if (updatePrimaryKey(primaryKeyVo)) {
                return primaryKeyVo;
            }
        }
        // 多次重试还不能获取，则抛出异常
        throw new LoadPrimaryKeyException("Can't update primaryKey " + primaryKeyName + " with retry:" + GenIdConstant.RETRY_COUNT);
    }

    @Resource(name = "primaryKeyTransactionManager")
    private PlatformTransactionManager transactionManager;

    /**
     * 更新操作加上独立事务，防止被嵌套到其他事务中导致回滚
     * @param primaryKeyVo
     * @return
     */
    private boolean updatePrimaryKey(PrimaryKeyVo primaryKeyVo){
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW));
        try{
            int updateResult = primaryKeyMapper.updatePrimaryKey(primaryKeyVo);
            transactionManager.commit(status);
            return updateResult == GenIdConstant.UPDATE_1;
        }catch (Exception e){
            transactionManager.rollback(status);
            log.error("updatePrimaryKey failed", e);
            return false;
        }

    }

//    public void setExpandKeyExecutor(Executor expandKeyExecutor){
//        this.expandKeyExecutor = expandKeyExecutor;
//    }
//
//    public void setLoadPrepBuffexecutor(Executor loadPrepBuffexecutor){
//        this.loadPrepBuffexecutor = loadPrepBuffexecutor;
//    }

//    public void setPrimaryKeyDao(PrimaryKeyDao primaryKeyDao){
//        this.primaryKeyDao = primaryKeyDao;
//    }


    public void setPrimaryKeyMapper(PrimaryKeyMapper primaryKeyMapper) {
        this.primaryKeyMapper = primaryKeyMapper;
    }
}
